package org.su.generalmvc.scanner;

import org.su.generalmvc.IParamBinder;
import org.su.generalmvc.IEditor;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.su.generalmvc.annotaion.*;
import org.su.generalmvc.core.*;
import org.su.generalmvc.util.*;

import javax.validation.Valid;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * 扫描控制器，分析项目中的所有Controller结尾的类
 * 将有RequestMapiing注解的method方法封装为ActionProxy对象，将mthod的参数列表和注解封装为ActionConfig对象
 * 将有@PostConstruct和@PreDestroy注解的method方法封装为LifeCyle对象，并加入到LifeCycleManager中做对象
 * 生命周期管理（如果是spring接管bean管理，这步工作由spring完成）
 * 
 * @author yangchuan
 * 
 */
public class ActionScanner {

	private Map<String, ActionProxy> actions = new HashMap<String, ActionProxy>();
	private Map<String, ActionProxy> getActions = new HashMap<String, ActionProxy>();
	private Map<String, ActionProxy> postActions = new HashMap<String, ActionProxy>();
	private Map<String, ActionProxy> allActions = new HashMap<String, ActionProxy>();

	protected static final Logger logger = LoggerFactory.getLogger(ActionScanner.class);

	private IParamBinder paramBinder = null;//DataBinder.getInstence();

	private IXsshApplicationContext context;
	
	private IAfterAcoinScaned afterAcoinScaned;


	public ActionScanner(IXsshApplicationContext context) {
		super();
		this.context = context;
        paramBinder = context.getParamBinder();
		afterAcoinScaned = (IAfterAcoinScaned)context.getBean("afterAcoinScaned");
		if(afterAcoinScaned == null){
			afterAcoinScaned = new DefaultActionScaned();
		}
	}

	/**
	 * 控制器后缀
	 */
	private static final String CONTROLLER_SUFFIX = "Controller";

	private static final String ACTION_SUFFIX = "Action";

	/**
	 * 得到类路径下包路径的绝对路径
	 * 
	 * @param path
	 *            包路径（com.xssh的路径为com/xssh）
	 * @return
	 */
	private URL getURL(String path) {
		ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
		URL resourceUrl = classLoader.getResource(path);
		return resourceUrl;
	}

	/**
	 * 1. 遍历所有目录 2. 处理注解类，进行适配类匹配 3. 放入路径映射map
	 * 
	 * @throws java.io.IOException
	 * @throws java.net.URISyntaxException
	 * @throws ClassNotFoundException
	 */
	public void publishActions(String path, Map<String, XsshPackage> _packages)
			throws URISyntaxException, ClassNotFoundException {
		
		URL resourceUrl = getURL(path);
		if (resourceUrl != null) {
			File dir = new File(resourceUrl.toURI());
			if (!path.endsWith("/")) {
				path = path + "/";
			}
			String packagePath = path.replaceAll("/", ".");
			directorize(dir, packagePath, _packages);
			context.setActions(actions);
			context.setPostActions(postActions);
			context.setGetActions(getActions);
			context.setAllActions(allActions);
		} else {
			logger.warn("dir is empty:" + path);
		}
	}

	public Map<String, ActionProxy> getActions() {
		return actions;
	}

	/**
	 * 扫描目录下的注解类
	 * 
	 * @param dir
	 *            目录对象
	 * @param packagePath
	 *            以"."结尾的包名
	 * @throws ClassNotFoundException
	 */
	private void directorize(File dir, String packagePath,
			Map<String, XsshPackage> _packages) throws ClassNotFoundException {
		File[] files = dir.listFiles();
		if (files != null) {
			for (File file : files) {
				if (file.isDirectory()) {
					directorize(file, packagePath + file.getName() + ".",
							_packages);
				} else {
					String fileName = file.getName();

					if (fileName.endsWith(".class")) {
						
						if(packagePath.startsWith(".")){
							fileName = packagePath.substring(1) + fileName;
						}else{
							fileName = packagePath + fileName;
						}
						parseClass(fileName, _packages);
					}
				}
			}
		}
	}

	/**
	 * 分析类数据
	 * 
	 * @param className
	 */
	private void parseClass(String className, Map<String, XsshPackage> _packages) {
		// if (className.endsWith(CONTROLLER_SUFFIX)) {
		try {
			className = className.substring(0, className.length() - 6);
			Class<?> clazz = Class.forName(className);
			if (clazz.getAnnotation(Controller.class) != null
					|| className.endsWith(CONTROLLER_SUFFIX)) {
				initController(clazz, _packages);
			}

		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		// }

	}

	/**
	 * 分析控制类，如果没有设置Controller注解则url地址和bean名称都去默认
	 * 
	 * @param clazz
	 *            要创建对象的类
	 * @return 创建的对象
	 */
	private void initController(Class<?> clazz, Map<String, XsshPackage> _packages)
			throws Exception {
		Object instance = null;
		String name = null;
		String actionSuffix = null;

		Controller controller = (Controller) clazz
				.getAnnotation(Controller.class);

		if (controller != null) {
			name = controller.name();
			if (StringUtils.isBlank(name)) {
				name = clazz.getSimpleName();
			}
		} else {
			name = clazz.getSimpleName();
		}
		
		XsshPackage xp = getXP(_packages, clazz.getPackage().getName(), controller);
		actionSuffix = getActtionSuffix(controller, xp);
		String baseURL = getControllerUrl(xp, clazz, controller);
		

		String beanName =   xp.getNamespace() + "/" + name;
		
		String viewdir = xp.getViewdir() + "/" + name;
		
		instance = context.addBean(beanName, clazz);
		
		logger.info("found controllers:[" + beanName + "] with class: "
                + clazz.getName());
		Method[] methods = clazz.getMethods();
		LifeCycle lifeCycle = null;

		for (Method m : methods) {

			RequestMapping rm = m.getAnnotation(RequestMapping.class);
			if (rm != null || m.getName().endsWith(ACTION_SUFFIX)) {
				actionMapping(instance, beanName, m, rm, xp, baseURL, viewdir,
						actionSuffix);
			}else	if (m.getAnnotation(Init.class) != null) {
				if (lifeCycle == null) {
					lifeCycle = new LifeCycle(instance);
				}
				lifeCycle.setInitMethod(m);
				logger.info("  add init: " + clazz.getSimpleName() + "."
                        + m.getName());
			}else if (m.getAnnotation(Destroy.class) != null) {
				if (lifeCycle == null) {
					lifeCycle = new LifeCycle(instance);
				}
				lifeCycle.setDestroyMethod(m);
				logger.info("  add destroy: " + clazz.getSimpleName() + "."
                        + m.getName());
			}else if (m.getAnnotation(Scheduling.class) != null) {
				Scheduling scheduling = m.getAnnotation(Scheduling.class);
				// 定时任务用seed值限定
				if (scheduling.serverId() == -1
						|| scheduling.serverId() == context.getServerId()) {
					logger.info("  add timer task: "
                            + clazz.getSimpleName() + "."
                            + m.getName());
					LifeCycleManager.getInstence().addTask(scheduling.values(), instance, m);
				}
			}
			

		}
		if (lifeCycle != null) {
			LifeCycleManager.getInstence().add(lifeCycle);
		}

	}
	
	

	/**
	 * 将有RequestMapiing注解的method方法封装为ActionProxy对象，
	 * 将mthod的参数列表和注解封装为ActionConfig对象
	 * 将有@PostConstruct和@PreDestroy注解的method方法封装为LifeCyle对象，
	 * 并加入到LifeCycleManager中做对象 生命周期管理（如果是spring接管bean管理，这步工作由spring完成）
	 * 
	 * @param method
	 *            Controller类中的方法
	 * @param instance
	 *            Controller类的实例
	 */
	public void actionMapping(Object instance,String beanName, Method method,
			RequestMapping rm, XsshPackage xp, String baseUrl, String viewBase,
			String actionSuffix) {
		Class<?> rt = method.getReturnType();
		/*if(!(rt.equals(String.class) || rt.equals(void.class))){
			logger.error(" this action method retun-type must be String or void who named "+instance.getClass().getName()+"."+method.getName());
			return;
		}
		*/
		ActionConfig ac = new ActionConfig();
		ac.setControllerName(beanName);
		ac.setInterceptors(xp.getInterceptorStack());

		Transaction transaction = method.getAnnotation(Transaction.class);
		if (null != transaction) {
			ac.setTransLeavel(transaction.value());
		}

		preparedParams(instance, method, ac);
		ac.setRm(rm);
		ViewType type = ViewType.velocity;
		

		if (rm != null) {
			type = rm.value();
			ac.setPermiss(rm.permiss());
		}

		if (ac.getPermiss() == null) {
			ac.setPermiss(Permiss.login);
		}
		
		ac.setViewType(type);
		
		/**
		 * 设置默认action后缀
		 */
		
        String methodName = getSimpleActionName(method.getName());
		
		String vmBase = viewBase;
		if (!vmBase.endsWith("/")) {
			vmBase = vmBase + "/";
		}
		
		type.setRender(context, ac, vmBase + methodName);

		ActionProxy ap = new ActionProxy();
		ap.setActionConfig(ac);
		ap.setAction(method);
		ap.setController(instance);

		actionSuffix = getActtionSuffix(rm,actionSuffix);
		String url = getActtionUrl(methodName, rm, baseUrl, actionSuffix);

		RequestMethod[] rms = null;
		if(rm != null) {
			rms = rm.method();
		}
		if(rms == null || rms.length == 0){
			Assert.isNull(
					allActions.get(url),
					"[register action failed] - the action mapiing must not be registered. the action mapping is:"
							+ url);
			allActions.put(url, ap);
		}else {
			for (RequestMethod item : rms) {
				switch (item) {
					case POST:
						Assert.isNull(
								postActions.get(url),
								"[register action failed] - the action mapiing must not be registered. the action mapping is:"
										+ url);
						postActions.put(url, ap);
						break;
					case GET:
						Assert.isNull(
								getActions.get(url),
								"[register action failed] - the action mapiing must not be registered. the action mapping is:"
										+ url);
						getActions.put(url, ap);
						break;
					default:
						Assert.isNull(
								allActions.get(url),
								"[register action failed] - the action mapiing must not be registered. the action mapping is:"
										+ url);
						allActions.put(url, ap);
						break;
				}
			}
		}


		logger.warn("[register action repeatedly] - the action mapiing must not be registered. the action mapping is:{}"
				, url);

		actions.put(url, ap);

		logger.info("loading action: [" + url + "]"
                + (StringUtils.isNotBlank(ac.getDefaultViewPath()) ? " and viewPath: " + ac.getDefaultViewPath() : ""));
		afterAcoinScaned.afterHandel(instance,method,url,ac);
	}

	public String getSimpleActionName(String name) {
		if (name.endsWith(ACTION_SUFFIX)) {
			name = name.toLowerCase().substring(0,
					name.length() - ACTION_SUFFIX.length());
		}
		return name;
	}

	/**
	 * 预处理参数，将方法的参数列表以及参数类型封装到ActionConfig对象中
	 * 
	 * @param instance
	 * @param method
	 */
	private void preparedParams(Object instance, Method method, ActionConfig ac) {
		Class<?>[] paramClasses = method.getParameterTypes();
		//Type[] paramTypes = method.getGenericParameterTypes(); TODO 2016-5－10这里可以得到范型的具体类型，改造范型参数注入可以用这个方法
		Annotation[][] paramAnnotations = method.getParameterAnnotations();
		String[] _paramNames = null;
		try{
			if(JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_18){
				_paramNames = ReflectUtils.getMethodParamNames(method);

			}else {
				_paramNames = ReflectUtils.getMethodParamNames(
						instance.getClass(), method.getName());
			}
		}catch (ParamFoundException e) {
			logger.warn("get param name error:",e);
		}
		IEditor[] paramBinds = new IEditor[paramClasses.length];
		String[] paramNames = new String[paramClasses.length];
		//如果查找的参数名称数目和参数个数不相等，那么就不要这种方式查找参数。直接用注解方式查找
		if(_paramNames == null || _paramNames.length != paramClasses.length){
			logger.warn("{} 's params  get error,paramTypes.length is {} but _paramNames.length is {}",instance.getClass().getName(),paramClasses.length,_paramNames == null?-1:_paramNames.length);
			_paramNames = null;

		}
		
		String failParams = null;
		for (int i = 0; i < paramClasses.length; i++) {
			Class<?> paramClass = paramClasses[i];
			IEditor editor = paramBinder.findEditor(paramClass);
				if(ObjectUtils.isEmpty(_paramNames) ){
					if (!ObjectUtils.isEmpty(paramAnnotations[i])) {
						paramNames[i] = ((Param) paramAnnotations[i][0]).value();
						ContentType cType = ((Param) paramAnnotations[i][0]).type();
						if(cType.equals(ContentType.json)){
							editor = paramBinder.findEditor(ContentType.json.getClass());
							ac.getBeanParams().put(paramNames[i], paramClass);
						}
					}else {
						failParams += "," + i;
					}
				}else{
					paramNames[i] =_paramNames[i];
				}
				/*paramNames[i] = ObjectUtils.isEmpty(_paramNames) ? null
						: _paramNames[i];*/

			/**
			 * -------------------------------
			 * 将复合类型数据记录到actionConfig的复合类型参数缓存中(3.13)
			 */
			if (paramBinder.isCustomEditor(editor)) {
				Class<?> temp = paramClass;
				if(Collection.class.isAssignableFrom(temp)){
					Type fieldType = method.getGenericParameterTypes()[i];
					fieldType = ((ParameterizedType)fieldType).getActualTypeArguments()[0];
					temp = (Class)fieldType;
				}else if(temp.isArray()){
					String cname = temp.getCanonicalName();
					cname = cname.substring(0,cname.length()-2);
					try {
						temp = Class.forName(cname);
					} catch (ClassNotFoundException e) {
						logger.error("handle type {} faild",paramClass.getName(),e);
					}
				}
					if (!paramBinder.isBaseEditor(temp) && !context.getPojoContext().containsKey(temp.getName())) {
						PojoWriteMehtodScanner.scanMthod(temp);
					}
					ac.getBeanParams().put(paramNames[i], paramClass);
			}
			// -------------------结束--------

			paramBinds[i] = editor;
		}
		ac.setParamBinds(paramBinds);
		ac.setParamNames(paramNames);
		if(failParams != null){
			logger.warn("未找到参数名称,请将这些参数用@param注解："+failParams+":"+instance.getClass().getName()+"."+method.getName());
		}
	}


	public static void main(String[] args){

	}




	/**
	 * 获取类对应的xp对象
	 * @param _packages  roleIn为key的配置包对象
	 * @param pakeName 控制器包名称
	 * @param controller 控制器注解对象
	 * @return
	 */
	public XsshPackage getXP(Map<String, XsshPackage> _packages, String pakeName,Controller controller){
		
		XsshPackage xp = null;
		if (controller != null && StringUtils.isNotBlank(controller.packageName())) {
			xp = context.getPackage(controller.packageName());
		}else{
			xp = _packages.get(pakeName);
		}
		if (xp == null) {
			xp = context.getPackage(XsshPackage.DEFAULT_PACKAGE);
		}
		
		return xp;
		
	}
	
	/**
	 * 得到类对应的url后缀设置
	 * @param controller
	 * @param xp
	 * @return
	 */
	public String getActtionSuffix(Controller controller,XsshPackage xp){
		String result = null;
		if (controller != null && StringUtils.isNotBlank(controller.actionSuffix())) {
				result = controller.actionSuffix();
		}else if(StringUtils.isNotBlank(xp.getActionSuffix())){
			result = xp.getActionSuffix();
		}else{
			result = context.getConfig().getActionSuffix();
		}
		return result;
	}
	
	/**
	 * 得到action级别的url后缀设置
	 * @param rm
	 * @param actionSuffix
	 * @return
	 */
	public String getActtionSuffix(RequestMapping rm ,String actionSuffix){
		String result = actionSuffix;
		if (rm != null && StringUtils.isNotBlank(rm.suffix())) {
				result = rm.suffix();
		}
		return result;
	}
	
	
  /**
   * 得到action之前的url地址
   * @param xp
   * @param clazz
   * @param controller
   * @return
   */
   public String getControllerUrl(XsshPackage xp,Class<?> clazz,Controller controller){
	   String result ="";
	   String base = "";
	   if(StringUtils.isNotBlank(xp.getNamespace())){
		   result = xp.getNamespace();
	   }
	   if(controller != null && StringUtils.isNotBlank(controller.url())){
		   base = controller.url();
	   }else{
		  base = clazz.getSimpleName();// .toLowerCase();
			if (base.endsWith(CONTROLLER_SUFFIX)) {
				base = base.substring(0,
						base.length() - CONTROLLER_SUFFIX.length());
			}
	   }
	   if (!base.startsWith("/")) {
			base = "/" + base;
		}
	   result = result+ base;
	   return result;
	   
   }
	
	
   /**
    * 得到Action上的url地址
    * @param methodName
    * @param rm
    * @param base
    * @param actionSuffix
    * @return
    */
	public String getActtionUrl(String methodName, RequestMapping rm,String base, String actionSuffix){

		String url = rm != null ? rm.url() : null;
		if (StringUtils.isBlank(url)) {
			url = "/" + methodName;// .toLowerCase();
		}
		url = base + url + actionSuffix;
		url = url.replaceAll("//", "/");
		url = url.replaceAll("//", "/");//类，方法，后缀同时为/.
		return url;
	}	

}
